//
//  ViewController.m
//  whfdemo006
//
//  Created by 王洪飞 on 2024/6/16.
//

#import "ViewController.h"
#import "GLProgram.h"
#import <OpenGLES/ES3/gl.h>
#import <OpenGLES/ES3/glext.h>

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
@interface ViewController ()
{
    GLKMatrix4 modelViewMatrix;
    GLKMatrix4 projectMatrix;
    GLfloat dWidth, dHeight;
    
    GLuint vaoId, screenVAO;
    GLuint s_texture;
    GLuint framebuffer;
    GLuint depthRenderbuffer;
    GLuint texture1;
    GLint texWidth, texHeight;
    GLint maxRenderbufferSize;
}
@property (nonatomic, strong) EAGLContext *mContext;
@property (nonatomic, strong) GLProgram *program;
@property (nonatomic, strong) GLProgram *baseProgram;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    texWidth = 256;
    texHeight = 256;
    GLint maxRenderbufferSize;
    dWidth = self.view.bounds.size.width;
    dHeight = self.view.bounds.size.height;
    self.mContext = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES3];
    GLKView *view = (GLKView *)self.view;
    view.context = self.mContext;
    [EAGLContext setCurrentContext:self.mContext];
    [self initMVP];
    [self setupProgram];
    [self setupBaseProgram];
    [self loadTexture1];
    [self prepareData];
    [self prepareScreenData];
    [self setupFramebuffer];
  
    
}
-(void)setupFramebuffer{
    glGetIntegerv(GL_MAX_RENDERBUFFER_SIZE, &maxRenderbufferSize);
    // check if gl_max_render_size is >= texWidth and texHeight
    if(maxRenderbufferSize <= texWidth || maxRenderbufferSize <= texHeight){
        NSLog(@"error");
        return;
    }
    
    // generate framebuffer,renderbuffer, and texture object names
    glGenFramebuffers(1, &framebuffer);
    // bind the framebuffer
    glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
    
    glGenRenderbuffers(1, &depthRenderbuffer);
    glGenTextures(1, &texture1);
    // bind texture and load the texture mip level 0
    // texels are rgb565
    // no texels need to be specified as we are going to draw into the texture
    glBindTexture(GL_TEXTURE_2D, texture1);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, texWidth, texHeight, 0, GL_RGB, GL_UNSIGNED_BYTE, NULL);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    // bind render buffer and create a 16 bit depth buffer
    // width and height of renderbuffer = width and height of the texture
    glBindRenderbuffer(GL_RENDERBUFFER, depthRenderbuffer);
    glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT16, texWidth, texHeight);

    // speicify texture as color attachment
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture1, 0);
    // specify depth_renderbuffer as depth attachment
    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, depthRenderbuffer);
    
    GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
    if(status == GL_FRAMEBUFFER_COMPLETE){
        NSLog(@"check framebuffer success");
    } else {
        NSLog(@"check framebuffer failed");
    }
    glBindFramebuffer(GL_FRAMEBUFFER, 0);
}


-(void)initMVP {
    projectMatrix = GLKMatrix4MakePerspective(GLKMathDegreesToRadians(85.0), dWidth / dHeight, 0.1f, 20.0f);
    modelViewMatrix = GLKMatrix4MakeLookAt(0.0, 0.0, 3.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0);
    
}

-(void)loadTexture1 {
    GLubyte pixels[] = {
        255,0,0,0,
        0,255,0,0,
        0,0,255,0,
        0,255,255,0,
    };
//    GLuint texture;
    glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
    
    glGenTextures(1, &s_texture);
    glBindTexture(GL_TEXTURE_2D, s_texture);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 2, 2, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
}


-(void)setupBaseProgram {
    NSString *vSaderStr = @""
    "#version 300 es\n"
    "layout(location = 0) in vec3 vPos;\n"
    "layout(location = 1) in vec2 vCoord;\n"
  
    "out vec2 s_coord;\n"
    "void main(){\n"
    "   s_coord = vCoord;\n"
//    "   gl_Position = projection * mv * vec4(vPos, 1.0);\n"
    "   gl_Position = vec4(vPos, 1.0);\n"
    "}";
    NSString *fShaderStr = @""
    "#version 300 es\n"
    "precision mediump float;\n"
    "uniform sampler2D texture1;\n"
    "uniform sampler2D s_texture;\n"
    "in vec2 s_coord;\n"
    "out vec4 FragColor;\n"
    "void main(){\n"
    "   FragColor = texture(s_texture, s_coord);\n"
//    "   FragColor = vec4(s_coord.x, s_coord.y, 0, 1);\n"
    "}";
    self.baseProgram = [[GLProgram alloc] initWithVertexShaderString:vSaderStr fragmentShaderString:fShaderStr];
    if ([self.baseProgram link]) {
        NSLog(@"check base link ");
    }
}

-(void)setupProgram{
    NSString *vSaderStr = @""
    "#version 300 es\n"
    "layout(location = 0) in vec3 vPos;\n"
    "layout(location = 1) in vec3 vColor;\n"
    "layout(location = 2) in vec2 vCoord;\n"
    "uniform mat4 projection;\n"
    "uniform mat4 mv;\n"
    "out vec2 s_coord;\n"
    "void main(){\n"
    "   s_coord = vCoord;\n"
    "   gl_Position = projection * mv * vec4(vPos, 1.0);\n"
//    "   gl_Position = vec4(vPos, 1.0);\n"
    "}";
    NSString *fShaderStr = @""
    "#version 300 es\n"
    "precision mediump float;\n"
    "uniform sampler2D s_texture;\n"
    "in vec2 s_coord;\n"
    "out vec4 fragColor;\n"
    "void main(){\n"
    "   fragColor = texture(s_texture, s_coord);\n"
//    "   fragColor = vec4(s_coord,1,1);\n"
    "}";
    self.program = [[GLProgram alloc] initWithVertexShaderString:vSaderStr fragmentShaderString:fShaderStr];
    if ([self.program link]) {
        NSLog(@"program link");
       
    }
}


-(void)prepareScreenData{
    GLfloat quadVertices[] = {
        -1.0f, 1.0f, 0.0f,         0.0f, 0.0f,//左上
        1.0f, 1.0f, 0.0f,           1.0f, 0.0f,//右上
        -1.0f, -1.0f, 0.0f,           0.0f, 1.0f,//左下
        1.0f, -1.0f, 0.0f,            1.0f, 1.0f,//右下
        0.0f, 0.0f, 1.0f,           0.5f, 0.5f,//顶点
    };
    GLubyte indices[] = {
        0, 3, 2,
        0, 1, 3,
    };
    GLuint screenVBO, screenEBO;
    glGenVertexArrays(1, &screenVAO);
    glBindVertexArray(screenVAO);
    
    glGenBuffers(1, &screenVBO);
    glBindBuffer(GL_ARRAY_BUFFER, screenVBO);
    glBufferData(GL_ARRAY_BUFFER, sizeof(quadVertices), quadVertices, GL_STATIC_DRAW);

    glGenBuffers(1, &screenEBO);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, screenEBO);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
    
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (float*)NULL);
    glEnableVertexAttribArray(0);

    glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (float*)NULL + 3);
    glEnableVertexAttribArray(1);
    glBindVertexArray(0);
    
}

-(void)prepareScreenData1{
    GLfloat quadVertices[] = {
        // positions   // texCoords
        -1.0f,  1.0f, 0.0f,  0.0f, 1.0f,
        -1.0f, -1.0f, 0.0f,  0.0f, 0.0f,
         1.0f, -1.0f, 0.0f,  1.0f, 0.0f,

         1.0f,  1.0f, 0.0f,  1.0f, 1.0f
    };
    

    GLubyte indices[] = {
        0, 3, 2,
        0, 1, 3,
    };
    GLuint screenVBO, screenEBO;
    glGenVertexArrays(1, &screenVAO);
    glBindVertexArray(screenVAO);
    
    glGenBuffers(1, &screenVBO);
    glBindBuffer(GL_ARRAY_BUFFER, screenVBO);
    glBufferData(GL_ARRAY_BUFFER, sizeof(quadVertices), quadVertices, GL_STATIC_DRAW);

    glGenBuffers(1, &screenEBO);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, screenEBO);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
    
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (float*)0);
    glEnableVertexAttribArray(0);

    glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (float*)NULL + 3);
    glEnableVertexAttribArray(1);
    glBindVertexArray(0);
    
}

-(void)prepareData {
    GLfloat vertices[] = {
        -1.0f, 1.0f, 0.0f,      0.0f, 0.0f, 1.0f,       0.0f, 0.0f,//左上
        1.0f, 1.0f, 0.0f,       0.0f, 1.0f, 0.0f,       1.0f, 0.0f,//右上
        -1.0f, -1.0f, 0.0f,     1.0f, 0.0f, 1.0f,       0.0f, 1.0f,//左下
        1.0f, -1.0f, 0.0f,      0.0f, 0.0f, 1.0f,       1.0f, 1.0f,//右下
        0.0f, 0.0f, 1.0f,       1.0f, 1.0f, 1.0f,       0.5f, 0.5f,//顶点
    };
    GLubyte indices[] = {
        0, 3, 2,
        0, 1, 3,
    };
    glGenVertexArrays(1, &vaoId);
    glBindVertexArray(vaoId);
    GLuint vboId, eboId;
    glGenBuffers(1, &vboId);
    glBindBuffer(GL_ARRAY_BUFFER, vboId);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
    
    glGenBuffers(1, &eboId);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, eboId);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
    
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(float) * 8, (float *)0);
    glEnableVertexAttribArray(0);
    glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, sizeof(float) * 8, (float *)NULL + 6);
    glEnableVertexAttribArray(2);
    
    glBindBuffer(GL_ARRAY_BUFFER, 0);
    glBindVertexArray(0);
}

-(void)glkView:(GLKView *)view drawInRect:(CGRect)rect{
    GLint defaultFramebuffer = 0;
    glGetIntegerv(GL_FRAMEBUFFER_BINDING, &defaultFramebuffer);
    glViewport(0, 0, view.drawableWidth, view.drawableHeight);
    
    glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
    
    
    glClearColor(1, 1, 1, 1);
    glClear(GL_COLOR_BUFFER_BIT);

    [self.program use];
    glBindVertexArray(vaoId);

    GLuint proSlot = [self.program uniformIndex:@"projection"];
    GLuint mvSlot = [self.program uniformIndex:@"mv"];
//    NSLog(@"draw %u : %u", proSlot, mvSlot);
    glUniformMatrix4fv(proSlot, 1, GL_FALSE, (GLfloat *)&projectMatrix.m[0]);
    glUniformMatrix4fv(mvSlot, 1, GL_FALSE, (GLfloat *)&modelViewMatrix.m[0]);

    glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_BYTE, NULL);
    

  
    glBindFramebuffer(GL_FRAMEBUFFER, defaultFramebuffer);
    glViewport(0, 0, view.drawableWidth, view.drawableHeight);
    [((GLKView *) self.view) bindDrawable];
//    
    glClearColor(1, 0.5, 1, 1);
    glClear(GL_COLOR_BUFFER_BIT);
    [self.baseProgram use];
    glBindVertexArray(screenVAO);
    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, texture1);
    glActiveTexture(GL_TEXTURE1);
    glBindTexture(GL_TEXTURE_2D, s_texture);

    glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_BYTE, NULL);

    
}

@end
